from pyModbusTCP.server import ModbusServer, DataBank
from struct import pack, unpack
import time
import random

# --- Modbus 服务器配置 ---
HOST = "0.0.0.0" # 监听所有可用网络接口
PORT = 5002      # 常用 Modbus TCP 端口

# --- 初始化 Modbus 服务器 ---
server = ModbusServer(HOST, PORT, no_block=True)
server.start()
print(f"Modbus 服务器已启动在 {HOST}:{PORT}")

# --- 辅助函数：向数据区写入不同类型的数据 ---

# 写入 32 位浮点数 (FLOAT)
# 修正：移除多余的 'server_instance' 参数
def write_float_to_databank(address, value):
    # 将浮点数打包成大端字节序 (Modbus 浮点数的标准)
    float_bytes = pack('>f', value)
    # 将 4 个字节解包成两个 16 位无符号整数 (寄存器)
    reg1, reg2 = unpack('>HH', float_bytes)
    # 通过全局的 'server' 对象访问其数据区来写入寄存器
    server.data_bank.set_holding_registers(address - 1, [reg1, reg2])
    # print(f"  浮点数 {value:.2f} 已写入保持寄存器[{address}]") # 用于调试

# 写入 16 位无符号整数 (UINT16)
# 修正：移除多余的 'server_instance' 参数
def write_uint16_to_databank(address, value):
    # 确保值在 16 位范围内
    value = int(value) & 0xFFFF
    # 通过全局的 'server' 对象访问其数据区来写入寄存器
    server.data_bank.set_holding_registers(address - 1, [value])
    # print(f"  无符号 16 位整数 {value} 已写入保持寄存器[{address}]") # 用于调试

# 写入 32 位无符号整数 (UINT32)
# 修正：移除多余的 'server_instance' 参数
def write_uint32_to_databank(address, value):
    # 将无符号 32 位整数打包成大端字节序
    uint32_bytes = pack('>I', value)
    # 将 4 个字节解包成两个 16 位无符号整数 (寄存器)
    reg1, reg2 = unpack('>HH', uint32_bytes)
    # 通过全局的 'server' 对象访问其数据区来写入寄存器
    server.data_bank.set_holding_registers(address - 1, [reg1, reg2])
    # print(f"  无符号 32 位整数 {value} 已写入保持寄存器[{address}]") # 用于调试

# 写入布尔量 (线圈或离散输入)
# 修正：移除多余的 'server_instance' 参数
def write_coil_to_databank(address, value):
    # 将布尔值写入线圈数据区
    server.data_bank.set_coils(address - 1, [value])
    # print(f"  布尔量 {value} 已写入线圈[{address}]") # 用于调试

# --- Modbus 地址与 PLC 数据点对应关系 ---

# 原始温度模拟 (浮点数) - 保持寄存器
# 对应 Neuron 地址: 402001
TEMP_ADDRESS = 2001

# --- 切割机模拟 (Cutting Machine Simulation) ---
# PLC 信息:
# - 状态: 0:空闲, 1:运行中, 2:故障
# - 切割计数: 总切割次数
# - 当前速度: 切割头当前移动速度
# - 刀片寿命: 刀片剩余寿命百分比
# - 电机过载报警: 布尔量，是否过载

# **状态** (UINT16) - 保持寄存器
# 对应 Neuron 地址: 402003
CUT_STATUS_ADDRESS = 2003
# **切割计数** (UINT32，需要两个寄存器) - 保持寄存器
# 对应 Neuron 地址: 402004
CUT_COUNT_ADDRESS = 2004
# **当前速度** (浮点数 FLOAT) - 保持寄存器
# 对应 Neuron 地址: 402006
CUT_SPEED_ADDRESS = 2006
# **刀片寿命剩余** (UINT16 百分比) - 保持寄存器
# 对应 Neuron 地址: 402008
CUT_BLADE_LIFE_ADDRESS = 2008
# **电机过载报警** (布尔量 BOOL) - 线圈
# 对应 Neuron 地址: 1!1001
CUT_MOTOR_OVERLOAD_COIL = 1001

# --- 折弯机模拟 (Bending Machine Simulation) ---
# PLC 信息:
# - 状态: 0:空闲, 1:运行中, 2:维护中
# - 当前角度: 折弯头当前角度
# - 折弯周期计数: 完成的折弯周期次数
# - 液压压力: 液压系统当前压力

# **状态** (UINT16) - 保持寄存器
# 对应 Neuron 地址: 402010
BEND_STATUS_ADDRESS = 2010
# **当前角度** (浮点数 FLOAT) - 保持寄存器
# 对应 Neuron 地址: 402011
BEND_ANGLE_ADDRESS = 2011
# **折弯周期计数** (UINT32) - 保持寄存器
# 对应 Neuron 地址: 402013
BEND_CYCLE_COUNT_ADDRESS = 2013
# **液压压力** (浮点数 FLOAT) - 保持寄存器
# 对应 Neuron 地址: 402015
BEND_HYDRAULIC_PRESSURE_ADDRESS = 2015

# --- 喷涂线模拟 (Painting Line Simulation) ---
# PLC 信息:
# - 风扇状态: 0:关闭, 1:开启
# - 当前喷涂流量: 每秒喷涂量
# - 剩余喷涂时间: 当前喷涂任务的剩余时间
# - 油漆余量: 油漆罐剩余百分比
# - 机械臂故障: 布尔量，机械臂是否故障

# **风扇状态** (布尔量 BOOL) - 线圈
# 对应 Neuron 地址: 1!1002
PAINT_FAN_COIL = 1002
# **当前喷涂流量** (浮点数 FLOAT) - 保持寄存器
# 对应 Neuron 地址: 402017
PAINT_FLOW_ADDRESS = 2017
# **剩余喷涂时间** (UINT16 秒) - 保持寄存器
# 对应 Neuron 地址: 402019
PAINT_TIME_REMAINING_ADDRESS = 2019
# **油漆余量** (UINT16 百分比) - 保持寄存器
# 对应 Neuron 地址: 402020
PAINT_LEVEL_ADDRESS = 2020
# **机械臂故障** (布尔量 BOOL) - 线圈
# 对应 Neuron 地址: 1!1003
PAINT_ROBOT_FAULT_COIL = 1003

print("\n--- 正在模拟动态数据 ---")

# --- 主循环：更新动态数据 ---
try:
    cut_counter = 0 # 切割计数器初始化
    bend_cycle_counter = 0 # 折弯周期计数器初始化
    while True:
        # --- 更新原始温度 ---
        # 修正：移除多余的 'server' 参数
        current_temp = random.uniform(20.0, 30.0)
        write_float_to_databank(TEMP_ADDRESS, current_temp)
        print(f"温度 (HR{TEMP_ADDRESS}): {current_temp:.2f}")

        # --- 切割机数据 ---
        # 模拟状态变化 (空闲, 运行中, 故障)
        cut_status = random.choices([0, 1, 2], weights=[0.7, 0.25, 0.05], k=1)[0]
        # 修正：移除多余的 'server' 参数
        write_uint16_to_databank(CUT_STATUS_ADDRESS, cut_status)
        if cut_status == 1: # 如果正在运行
            cut_counter += 1
            current_cut_speed = random.uniform(0.5, 2.5) # 米/秒
            current_blade_life = random.randint(10, 100) # 百分比
        else: # 如果空闲或故障，速度为 0
            current_cut_speed = 0.0
            current_blade_life = random.randint(50, 100) if cut_status == 0 else random.randint(1, 49) # 故障时刀片寿命较低

        # 修正：移除多余的 'server' 参数
        write_uint32_to_databank(CUT_COUNT_ADDRESS, cut_counter)
        write_float_to_databank(CUT_SPEED_ADDRESS, current_cut_speed)
        write_uint16_to_databank(CUT_BLADE_LIFE_ADDRESS, current_blade_life)
        # 模拟偶尔的电机过载 (2% 几率)
        write_coil_to_databank(CUT_MOTOR_OVERLOAD_COIL, random.random() < 0.02)

        print(f"切割机: 状态({cut_status}), 计数({cut_counter}), 速度({current_cut_speed:.2f}), 刀片寿命({current_blade_life}%), 电机过载({server.data_bank.get_coils(CUT_MOTOR_OVERLOAD_COIL-1)[0]})")

        # --- 折弯机数据 ---
        bend_status = random.choices([0, 1, 2], weights=[0.6, 0.35, 0.05], k=1)[0]
        write_uint16_to_databank(BEND_STATUS_ADDRESS, bend_status)
        if bend_status == 1: # 如果正在运行
            bend_cycle_counter += 1
            current_bend_angle = random.uniform(85.0, 95.0) # 度
            current_hydraulic_pressure = random.uniform(15.0, 25.0) # 兆帕
        else: # 如果空闲或维护
            current_bend_angle = 0.0
            current_hydraulic_pressure = 0.0

        write_float_to_databank(BEND_ANGLE_ADDRESS, current_bend_angle)
        write_uint32_to_databank(BEND_CYCLE_COUNT_ADDRESS, bend_cycle_counter)
        write_float_to_databank(BEND_HYDRAULIC_PRESSURE_ADDRESS, current_hydraulic_pressure)

        print(f"折弯机: 状态({bend_status}), 角度({current_bend_angle:.2f}), 周期({bend_cycle_counter}), 液压({current_hydraulic_pressure:.2f})")

        # --- 喷涂线数据 ---
        paint_fan_status = random.random() < 0.7 # 70% 几率风扇开启
        write_coil_to_databank(PAINT_FAN_COIL, paint_fan_status)

        current_paint_flow = random.uniform(5.0, 15.0) if paint_fan_status else 0.0 # 毫升/秒
        write_float_to_databank(PAINT_FLOW_ADDRESS, current_paint_flow)

        paint_time_remaining = random.randint(0, 300) # 秒
        write_uint16_to_databank(PAINT_TIME_REMAINING_ADDRESS, paint_time_remaining)

        paint_level = random.randint(0, 100) # 百分比
        write_uint16_to_databank(PAINT_LEVEL_ADDRESS, paint_level)

        write_coil_to_databank(PAINT_ROBOT_FAULT_COIL, random.random() < 0.01) # 1% 几率机器人故障

        print(f"喷涂线: 风扇({paint_fan_status}), 流量({current_paint_flow:.2f}), 剩余时间({paint_time_remaining}), 余量({paint_level}%), 机器人故障({server.data_bank.get_coils(PAINT_ROBOT_FAULT_COIL-1)[0]})")

        print("-" * 40) # 分隔符，方便阅读
        time.sleep(5) # 每 5 秒更新一次数据

except KeyboardInterrupt:
    print("\n正在停止服务器...")
finally:
    server.stop()
    print("服务器已停止。")